using System; 
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Xml;

namespace Fadd.Data.Mappings
{
    /// <summary>
    /// Loads mappings from embedded XML files.
    /// </summary>
    /// <remarks>
    /// <para>
    /// En mapping file can look like this:
    /// <code>
    /// <![CDATA[
    /// <?xml version="1.0" encoding="utf-8" ?>
    /// <class name="Gate.BusinessObjects.AdminTodo, Gate.BusinessObjects" table="admin_todos">
    ///   <id name="Id" column="id" type="integer" generator="autoincrement" />
    ///   <property name="Name" column="name" type="string"/>
    ///   <property name="SiteId" column="site_id" type="integer"/>
    ///   <property name="MethodName" column="action" type="string"/>
    ///   <property name="MethodId" column="action_id" type="string"/>
    ///   <property name="Controller" column="controller" type="string"/>
    ///   <property name="Description" column="description" type="string"/>
    /// </class>
    /// ]]>
    /// </code>
    /// </para>
    /// <para>
    /// class 'name' is a assembly qualified name.
    /// </para>
    /// <para>
    /// generator can be 'autoincrement' (works with databases such as SQL Server or MySQL), 'sequence' (works with PostgreSQL, looks for a sequence 
    /// with the name '[tablename]_id_seq'), 'guid' (Fadd.Data generates the guid), any other values are interpreted as 
    /// a sequence name (and will be used as such depending on the database type).
    /// </para>
    /// <para>
    /// Property/Id 'type' attribute is not yet used. 
    /// Will be used along with the 'length' attribute when creating/upgrading tables/columns.
    /// </para>
    /// </remarks>
    public class XmlLoader : ILoader
    {
        private readonly List<Assembly> _assemblies = new List<Assembly>();

        /// <summary>
        /// Initializes a new instance of the <see cref="XmlLoader"/> class.
        /// </summary>
        /// <param name="assembly">An assembly to look in.</param>
        /// <remarks>
        /// You can add more assemblies by using <see cref="AddAssembly"/>
        /// </remarks>
        public XmlLoader(Assembly assembly)
        {
            _assemblies.Add(assembly);
        }

        /// <summary>
        /// Add another assembly that mappings can be found in.
        /// </summary>
        /// <param name="assembly">Assembly to add.</param>
        public void AddAssembly(Assembly assembly)
        {
            if (!_assemblies.Contains(assembly))
                _assemblies.Add(assembly);
        }

        /// <summary>
        /// Scan after types that match the name.
        /// </summary>
        /// <param name="className">name of class to scan for</param>
        /// <returns>A list with matches.</returns>
        private ICollection<Type> FindClass(string className)
        {
            ICollection<Type> foundTypes = new List<Type>();
            foreach (Assembly assembly in _assemblies)
            {
                foreach (Type type in assembly.GetTypes())
                {
                    if (type.Name == className)
                        foundTypes.Add(type);
                }
            }

            return foundTypes;
        }

        private static Stream LoadConfigStream(Type type)
        {
            try
            {
                string fullName = type.Namespace + "." + type.Name + ".fdm.xml";
                return type.Assembly.GetManifestResourceStream(fullName);
            }
            catch (FileNotFoundException)
            {
                return null;
            }
        }

        #region ILoader Members

        /// <summary>
        /// Get an mapping.
        /// </summary>
        /// <param name="className">Name of class.</param>
        /// <returns><see cref="IMapping"/> if found; otherwise null.</returns>
        /// <exception cref="MappingException">Failed to find any attributes in the class element.</exception>
        public IMapping GetMapping(string className)
        {
            ICollection<Type> matches = FindClass(className);
            foreach (Type type in matches)
            {
                Stream stream = LoadConfigStream(type);
                if (stream == null) continue;


                XmlTextReader reader = new XmlTextReader(stream);
                XmlMappingReader mappingReader = new XmlMappingReader(reader, type);
                IMapping mapping = mappingReader.Read();
                if (mapping != null)
                    return mapping;
            }

            return null;
        }

        #endregion
    }
}